package at.btd.bsk;

import java.util.Timer;
import java.util.TimerTask;

import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import at.abraxas.amarino.Amarino;
import at.btd.bsk.SendTestDataDialog.SendTestListener;
import at.btd.bsk.entities.Trip;
import at.btd.bsk.entities.Waypoint;
import at.btd.bsk.gps.CurrentLocationOverlay;
import at.btd.bsk.gps.GPSListener;
import at.btd.bsk.gps.GPSManager;
import at.btd.bsk.gps.TripOverlay;
import at.btd.bsk.gps.TripStatistics;
import at.btd.bsk.observer.Observer;
import at.btd.bsk.util.Constants;
import at.btd.bsk.util.Date;
import at.btd.bsk.util.TimeManager;

import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;

public class MainActivity extends MapActivity implements Observer<TimeManager>, SendTestListener {
    private String deviceAddress = null;
    private int velocityLimit1 = 10; // in km/h
    private int velocityLimit2 = 25; // in km/h
    private long warnTimestamp = -1;
    private WarnStatus warnStatus = WarnStatus.NONE;
    private Trip currentTrip = null;
    /** statistics for current run */
    private TripStatistics statistics = null;
    private TimeManager timeManager = null;
    private GPSManager gpsManager = null;
    private GPSListener gpsListener = null;
    private Observer<Waypoint> waitObserver = null;

    private MapView mapView = null;
    private ProgressDialog progressDialog = null;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // retreive reference of MapView
        this.mapView = (MapView) findViewById(R.id.map);
        this.mapView.setBuiltInZoomControls(true);
        this.mapView.getController().setZoom(Constants.DEFAULT_ZOOM_LEVEL);

        this.currentTrip = new Trip(new Date(), 0, 0);
        this.statistics = new TripStatistics(this);

        this.mapView.getOverlays().add(new TripOverlay(this.currentTrip, this.mapView));
        this.mapView.getOverlays().add(new CurrentLocationOverlay(this, this.mapView));

        this.gpsListener = new GPSListener(this.currentTrip, this.mapView, this);
        this.gpsManager = new GPSManager(this);
        this.gpsManager.addObserver(this.gpsListener);
        this.gpsManager.addObserver(this.statistics);

        this.timeManager = new TimeManager(this);

        updateUI();

        Log.i(getClass().getName(), "In onCreate()");

        if (this.isEmulator()) {
            startTracking();
        } else {
            showWaitingDialog();
        }
    }

    @Override
    protected boolean isRouteDisplayed() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if ((keyCode == KeyEvent.KEYCODE_BACK)) {
            stopRun();
        }

        return super.onKeyDown(keyCode, event);
    }

    /* Creates the menu items */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        menu.add(0, Constants.MENU_SETTINGS, 1, this.getResources().getString(R.string.app_settings)).setIcon(android.R.drawable.ic_menu_preferences);
        menu.add(0, Constants.MENU_PAUSECONTINUE, 2, this.getResources().getString(R.string.run_pauseContinue)).setIcon(android.R.drawable.ic_menu_mylocation);
        menu.add(0, Constants.MENU_STOP, 3, this.getResources().getString(R.string.run_stop)).setIcon(android.R.drawable.ic_menu_save);
        menu.add(0, Constants.MENU_SENDDATA, 4, this.getResources().getString(R.string.arduino_sendTest)).setIcon(android.R.drawable.ic_menu_send);

        return true;
    }

    
    /* Handles menu item selections */
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case Constants.MENU_SETTINGS:
            Intent prefIntent = new Intent(this, PreferenceActivity.class);
            this.startActivity(prefIntent);

            return true;

        case Constants.MENU_PAUSECONTINUE:
            if (this.statistics.getTime().isPaused()) {
                this.continueRun();
            } else {
                this.pauseRun();
            }
            return true;

        case Constants.MENU_STOP:
            this.stopRun();

            return true;

        case Constants.MENU_SENDDATA:
            this.postTestNotificationToArduino();
            return true;
        }

        return false;
    }

    private void continueRun() {
        this.statistics.getTime().start();
    }

    private void pauseRun() {
        this.statistics.getTime().pause();
    }

    public void stopRun() {
        this.statistics.getTime().pause();
        updateRunStatistics();
        this.gpsManager.stop();
        this.timeManager.setEnabled(false);

        Amarino.disconnect(this, deviceAddress);
    }

    /**
     * updates the statistic-values in the run-class
     */
    private void updateRunStatistics() {
        this.currentTrip.setTimeInSeconds(this.statistics.getTime().getDurationInSeconds());
        this.currentTrip.setDistanceInKm(this.statistics.getDistanceInKm());
        this.currentTrip.setSpeed(this.statistics.getAverageSpeed());
    }

    /**
     * updates the User-Interface to show new values
     */
    private void updateUI() {
        /*
         * int hours = (int) statistics.getTime().getDisplayHours(); int minutes
         * = (int) statistics.getTime().getDisplayMinutes(); int seconds = (int)
         * statistics.getTime().getDisplaySeconds(); double distance =
         * statistics.getDistanceInKm();
         * 
         * textElapsedTime.setText(String.format("%02d:%02d:%02d", hours,
         * minutes, seconds)); textPace.setText(String.format("%02.2f", pace) +
         * "\n" + getResources().getString(R.string.app_unitPace));
         * textDistance.setText(String.format("%2.2f", distance) + "\n" +
         * getResources().getString(R.string.app_unitDistance));
         * textCalories.setText(getResources().getString(R.string.app_calories)
         * + ": " + String.format("%2.0f", calories) + " " +
         * getResources().getString(R.string.app_unitCalories));
         */
    }

    @Override
    public void notify(TimeManager param) {
        updateRunStatistics();
        updateUI();
    }

    private void startTracking() {
        // get the xml/preferences.xml preferences
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
        this.deviceAddress = prefs.getString("deviceAddress", "00:06:66:42:8A:B4");
        Amarino.connect(this, deviceAddress);

        String velocityString = prefs.getString("warnVelocity", "10");

        try {
            this.velocityLimit1 = Integer.parseInt(velocityString);
        } catch (Exception ex) {
            this.velocityLimit1 = 10;
        }

        velocityString = prefs.getString("warnVelocity2", "25");

        try {
            this.velocityLimit2 = Integer.parseInt(velocityString);
        } catch (Exception ex) {
            this.velocityLimit2 = 25;
        }

        // remove temporary observer
        if (this.waitObserver != null) {
            this.gpsManager.removeObserver(this.waitObserver);
        }

        this.statistics.getTime().start();
        this.timeManager.setEnabled(true);
        this.timeManager.execute();

        Toast toast = Toast.makeText(this, "Started tracking!", 1500);
        toast.show();
    }

    /**
     * Shows a progressdialog, as long as there is no acceptable gps-signal
     * strength
     */
    private void showWaitingDialog() {
        this.progressDialog = ProgressDialog.show(this, "", getResources().getString(R.string.run_gpsWait), true);
        // user can cancel with back-button
        this.progressDialog.setCancelable(true);
        this.timeManager.setEnabled(false);

        // if the user cancels, finish activity
        this.progressDialog.setOnCancelListener(new OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                MainActivity.this.progressDialog.dismiss();
                MainActivity.this.gpsManager.stop();
            }
        });

        // listen for new gps-waypoints and check current accuracy
        this.waitObserver = new Observer<Waypoint>() {
            @Override
            public void notify(Waypoint p) {
                if (MainActivity.this.progressDialog.isShowing()) {
                    Log.i(getClass().getName(), "Waiting for GPS: current Accuracy: " + p.getAccuracy());

                    // acceptable accuracy found?
                    if (p.hasAccuracy() && p.getAccuracy() < Constants.GPS_ACCURACY_BAD) {
                        // close dialog
                        MainActivity.this.progressDialog.dismiss();
                        // start gps-tracking
                        startTracking();
                    }
                }
            }

            public boolean isWaitObserver() {
                return true;
            }
        };

        this.gpsManager.addObserver(this.waitObserver);
    }

    public void postTestNotificationToArduino() {
        /*Random r = new Random();
        float testVelocity = 1 + r.nextInt(40);

        postVelocity(testVelocity);

        Toast toast = Toast.makeText(this, "Sent velocity: " + testVelocity + " km/h", 1000);
        toast.show();*/
        SendTestDataDialog dialog = new SendTestDataDialog(this,this);
        dialog.show();
    }

    public void postNotificationToArduino(Waypoint waypoint) {
        postVelocity(waypoint.getSpeed() * 3.6f);
    }

    private void postVelocity(float velocity) {
        Vibrator v = (Vibrator) getSystemService(MainActivity.VIBRATOR_SERVICE);
        
        Amarino.sendDataToArduino(this, deviceAddress, Constants.AMARINO_EVENT_VELOCITY, velocity / 3.6f);

        // warn the device permanently, if predefined velocity was exceeded
        if (velocity > this.velocityLimit2) {
            Amarino.sendDataToArduino(this, deviceAddress, Constants.AMARINO_EVENT_WARN, velocity / 3.6f);
            v.vibrate(1000);
            Log.i(getClass().getName(), "Started Vibrating!");
            this.warnStatus = WarnStatus.PERMANENTLY;
        }

        else {
            // only warn for 10 seconds
            if (velocity > this.velocityLimit1) {
                // if we were previously in mode permanently stop warning now
                if (this.warnStatus == WarnStatus.PERMANENTLY) {
                    Amarino.sendDataToArduino(MainActivity.this, deviceAddress, Constants.AMARINO_EVENT_STOP, 0);
                    Log.i(getClass().getName(), "Stopped Vibrating!");
                    this.warnStatus = WarnStatus.TEMPORARY;
                }

                if (this.warnStatus == WarnStatus.NONE && this.warnTimestamp == -1) {
                    Amarino.sendDataToArduino(this, deviceAddress, Constants.AMARINO_EVENT_WARN, velocity / 3.6f);
                    Log.i(getClass().getName(), "Started Vibrating!");
                    this.warnTimestamp = System.currentTimeMillis();
                    this.warnStatus = WarnStatus.TEMPORARY;

                    int delay = 10000;// in ms
                    
                    Timer timer = new Timer();

                    v.vibrate(delay);
                    // stop vibrating after 10 seconds
                    timer.schedule(new TimerTask() {
                        public void run() {
                            Amarino.sendDataToArduino(MainActivity.this, deviceAddress, Constants.AMARINO_EVENT_STOP, 0);
                            Log.i(getClass().getName(), "Stopped Vibrating!");
                        }
                    }, delay);
                }
            } 

            // Stop all warnings
            else {
                if (this.warnStatus != WarnStatus.NONE) {
                    Amarino.sendDataToArduino(MainActivity.this, deviceAddress, Constants.AMARINO_EVENT_WARN, velocity / 3.6f);
                    Log.i(getClass().getName(), "Started Vibrating!");
                    
                    this.warnTimestamp = System.currentTimeMillis();

                    int delay = 5000;// in ms

                    Timer timer = new Timer();

                    v.vibrate(delay);
                    // stop vibrating after 10 seconds
                    timer.schedule(new TimerTask() {
                        public void run() {
                            Amarino.sendDataToArduino(MainActivity.this, deviceAddress, Constants.AMARINO_EVENT_STOP, 0);
                            Log.i(getClass().getName(), "Stopped Vibrating!");
                        }
                    }, delay);
                }

                MainActivity.this.warnTimestamp = -1;
                this.warnStatus = WarnStatus.NONE;
            }
        }
    }

    private boolean isEmulator() {
        boolean isEmulator = false;

        TelephonyManager man = (TelephonyManager) getApplicationContext().getSystemService(MainActivity.TELEPHONY_SERVICE);

        if (man != null) {
            String devId = man.getDeviceSoftwareVersion();
            isEmulator = (devId == null);
        }

        return isEmulator;
    }

    @Override
    public boolean isWaitObserver() {
        // TODO Auto-generated method stub
        return false;
    }

    enum WarnStatus {
        NONE, TEMPORARY, PERMANENTLY
    }

    @Override
    public void doSendTestData(int velocity) {
        postVelocity(velocity);

        Toast toast = Toast.makeText(this, "Sent velocity: " + velocity + " km/h", 1000);
        toast.show();        
    }
}